﻿#region Copyright(c) 2006 ZO, All right reserved.
// -----------------------------------------------------------------------------
// Copyright(c) 2006 ZO, All right reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//   1.  Redistribution of source code must retain the above copyright notice,
//       this list of conditions and the following disclaimer.
//   2.  Redistribution in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//   3.  The name of the author may not be used to endorse or promote products
//       derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
// WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
// EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
// -----------------------------------------------------------------------------
#endregion


#region Using directives

using System;
using System.Globalization;
using System.Text;
using System.Xml;
using ZO.SmartCore.Core;
using ZO.SmartCore.Helpers;
using ZO.SmartCore.IO;

#endregion

namespace ZO.SmartCore.Xml
{
    /// <summary>
    /// Represents a single node in the XML document. 
    /// </summary>
    public abstract class Node : DisposableObject
    {

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="Node"/> class.
        /// </summary>
        protected Node()
        {
            _ChildNodes = new NodeList(this);
            _Namespaces = new NamespaceList(this);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Node"/> class.
        /// </summary>
        /// <param name="value">The value.</param>
        protected Node(string value)
            : this()
        {
            this._Value = value;
        }

        #endregion

        #region Destructor

        #endregion

        #region Fields
        private NodeList _ChildNodes;
        private string _Value;
        private NamespaceList _Namespaces;
        private Node _Parent;

        private bool _Independent;

        private XmlWriter writer;
        #endregion

        #region Events

        #endregion

        #region Operators

        #endregion

        #region Properties
        /// <summary>
        /// Gets or sets the value of the element in base 64.
        /// </summary>
        /// <value>The value as base64.</value>
        public string ValueAsBase64
        {
            get
            {
                byte[] buffer = Convert.FromBase64String(this.Value);
                return System.Text.Encoding.ASCII.GetString(buffer, 0, buffer.Length);
            }
            set
            {
                byte[] buffer = System.Text.Encoding.Default.GetBytes(value);
                this.Value = Convert.ToBase64String(buffer, 0, buffer.Length);
            }
        }

        /// <summary>
        /// Gets a value indicating whether this node has any child nodes. 
        /// </summary>
        /// <value>true if the node has child nodes; otherwise, false. </value>
        public virtual bool HasChildNodes
        {
            get
            {
                if (this.ChildNodes.Count > 0)
                {
                    return true;
                }

                return false;
            }
            // get
        }

        /// <summary>
        /// Gets a value indicating whether the current node can have a Value other than String.Empty. 
        /// </summary>
        public virtual bool HasValue
        {
            get
            {
                return (!String.IsNullOrEmpty(this.Value));
            } // get
        }


        /// <summary>When overridden in a derived class, gets the type of the current node.</summary>
        /// <returns>One of the <see cref="NodeType"></see> values.</returns>
        public abstract NodeType NodeType { get; }

        /// <summary>Gets or sets the value of the node.</summary>
        /// <returns>The value returned depends on the <see cref="NodeType"></see> of the node: Type Value Attribute The value of the attribute. CDATASection The content of the CDATA Section. Comment The content of the comment. Document null. DocumentFragment null. DocumentType null. Element null. You can use the <see cref="P:System.Xml.XmlElement.InnerText"></see> or <see cref="P:System.Xml.XmlElement.InnerXml"></see> properties to access the value of the element node. Entity null. EntityReference null. Notation null. ProcessingInstruction The entire content excluding the target. Text The content of the text node. SignificantWhitespace The white space characters. White space can consist of one or more space characters, carriage returns, line feeds, or tabs. Whitespace The white space characters. White space can consist of one or more space characters, carriage returns, line feeds, or tabs. XmlDeclaration The content of the declaration (that is, everything between &lt;?xml and ?&gt;). </returns>
        /// <exception cref="System.InvalidOperationException">Setting the value of a node that is not supposed to have a value (for example, an Element node). </exception>
        /// <exception cref="System.ArgumentException">Setting the value of a node that is read-only. </exception>
        public virtual string Value
        {
            get { return this._Value; }
            set { this._Value = value; }
        }

        /// <summary>
        /// Gets an Dictionary containing the list of namespaces for this node. 
        /// </summary>
        /// <remarks>Do Not</remarks>
        public NamespaceList Namespaces
        {
            get
            {
                return _Namespaces;
            } // get
        }

        /// <summary>
        /// Gets all the child nodes of the node.
        /// </summary>
        /// <value>The child nodes.</value>
        /// <returns>An collection that contains all the child nodes of the node.If there are no child nodes, this property returns an empty <see cref="System.Xml.XmlNodeList"></see>.</returns>
        public NodeList ChildNodes
        {
            get
            {
                return this._ChildNodes;
            }
        }
        /// <summary>Gets the parent of this node (for nodes that can have parents).</summary>
        /// <returns>The Node that is the parent of the current node. If a node has just been created and not yet added to the tree, or if it has been removed from the tree, the parent is null. For all other nodes, the value returned depends on the <see cref="P:System.Xml.XmlNode.NodeType"></see> of the node. The following table describes the possible return values for the ParentNode property.NodeType Return Value of ParentNode Attribute, Document, DocumentFragment, Entity, Notation Returns null; these nodes do not have parents. CDATA Returns the element or entity reference containing the CDATA section. Comment Returns the element, entity reference, document type, or document containing the comment. DocumentType Returns the document node. Element Returns the parent node of the element. If the element is the root node in the tree, the parent is the document node. EntityReference Returns the element, attribute, or entity reference containing the entity reference. ProcessingInstruction Returns the document, element, document type, or entity reference containing the processing instruction. Text Returns the parent element, attribute, or entity reference containing the text node. </returns>
        public virtual Node Parent
        {
            get
            {
                return this._Parent;
            }
            internal set
            {
                if ((value.NodeType != NodeType.Document) && (value.NodeType != NodeType.Attribute) && (value.NodeType != NodeType.Comment))
                {
                    this._Parent = value;
                }
            } // set

        }

        #endregion

        #region Methods

        /// <summary>
        /// Override This Method To Dispose Unmanaged Resources.
        /// </summary>
        protected override void DisposeManagedResources()
        {
            base.DisposeManagedResources();
            this._Namespaces.Dispose();
            this._ChildNodes.Dispose();
        }

        /// <summary>Adds the specified node to the end of the list of child nodes, of this node.</summary>
        /// <param name="newChild">The node to add</param>
        public virtual void AddChild(Node newChild)
        {
            if (newChild == null)
            {
                return;
            }
            this.ChildNodes.Add(newChild);

        }



        /// <summary>
        /// Removes this node from Parent Node.
        /// </summary>
        public virtual void Remove()
        {
            if (Parent != null)
            {
                this.Parent.ChildNodes.Remove(this);
            }
        }

        /// <summary>
        /// Removes all the child nodes and attributes of the current node. 
        /// </summary>
        public virtual void RemoveAllChildNodes()
        {
            this.ChildNodes.Clear();
        }

        /// <summary>
        /// Removes specified child node 
        /// </summary>
        /// <param name="oldChild">The node being removed.</param>
        public virtual void RemoveChild(Node oldChild)
        {
            if (oldChild == null)
            {
                return;
            }

            if (this.ChildNodes.Contains(oldChild))
            {
                this.ChildNodes.Remove(oldChild);
            }
        }

        /// <summary>
        /// Finds the first node in the child nodes of specified type.
        /// </summary>
        /// <param name="type">The type to search.</param>
        /// <returns>first node found</returns>
        internal Node FindChild(NodeType type)
        {
            if (this.ChildNodes.Count > 0)
            {
                foreach (Node node in this.ChildNodes)
                {
                    if (node.NodeType == type)
                        return node;
                }
                return null;
            }

            return null;
        }

        /// <summary>
        /// Returns the last ChildNode, doesnt matter of which type it is
        /// </summary>
        public Node LastNode
        {
            get
            {
                if (this.ChildNodes.Count > 0)
                {
                    return this.ChildNodes[this.ChildNodes.Count - 1];
                }
                return null;
            }
        }

        /// <summary>
        /// Returns the first ChildNode, doesnt matter of which type it is
        /// </summary>
        public Node FirstNode
        {
            get
            {
                if (this.ChildNodes.Count > 0)
                {
                    return this.ChildNodes[0];
                }
                return null;
            }
        }
 


        /// <summary>
        /// Returns a <see cref="System.String"></see> that represents the current <see cref="System.Object"></see>.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"></see> that represents the current <see cref="System.Object"></see>.
        /// </returns>
        public override string ToString()
        {

            return ToString(false);
        }

        

        /// <summary>
        /// Returns a <see cref="System.String"></see> that represents the current <see cref="System.Object"></see> independently.
        /// </summary>
        /// <returns>
        /// A <see cref="System.String"></see> that represents the current <see cref="System.Object"></see>.
        /// </returns>
        public virtual string ToString(bool independent)
        {

            _Independent = independent;
            StringWriter buffer;


            XmlWriterSettings settings = new XmlWriterSettings();
            settings.CloseOutput = true;
            settings.Indent = true;


            settings.ConformanceLevel = ConformanceLevel.Auto;

            if (this.NodeType == NodeType.Document)
            {
                Document doc = (Document)this;

                string encodingString = ReflectionHelper.GetDescription(doc.Encoding);

                System.Text.Encoding encoding;
                if (!String.IsNullOrEmpty(encodingString))
                {
                    encoding = System.Text.Encoding.GetEncoding(ReflectionHelper.GetDescription(doc.Encoding));
                }
                else
                {
                    encoding = System.Text.Encoding.UTF8;
                } // else

                settings.Encoding = encoding;
                buffer = new StringWriter(encoding);
            }
            else
            {
                buffer = new StringWriter(CultureInfo.CurrentCulture);
            } // else


            writer = XmlWriter.Create(buffer, settings);
            this.BuildXml(this);
            writer.Close();

            writer = null;
            _Independent = false;

            return buffer.ToString();

        }

        /// <summary>
        /// Builds the XML.
        /// </summary>
        /// <param name="node">The node.</param>
        private void BuildXml(Node node)
        {
            if (node.NodeType == NodeType.Document)
            {
                //Write the ProcessingInstruction node.
                // <?xml version="1.0" encoding="windows-1252"?> ...
                Document doc = node as Document;

                string processors = "";

                processors += "version=\"" + doc.Version + "\"";

                string encodingString = ReflectionHelper.GetDescription(doc.Encoding);

                if (!String.IsNullOrEmpty(encodingString))
                {
                    processors += " " + "encoding=\"" + encodingString + "\"";

                }


                writer.WriteProcessingInstruction("xml", processors);

                foreach (Node childNode in node.ChildNodes)
                {
                    BuildXml(childNode);
                }
            }


            if (node.NodeType == NodeType.Comment)
            {
                writer.WriteComment(node.Value);
            }

            if (node.NodeType == NodeType.CDATA)
            {
                writer.WriteCData(node.Value);
            }

            if (node.NodeType == NodeType.Element)
            {
                Element currentElement = (Element)node;

                string tempNamespace;

                if (!String.IsNullOrEmpty(currentElement.Prefix))
                {
                    tempNamespace = currentElement.Namespaces.LookupNamespace(currentElement.Prefix);

                    if (!String.IsNullOrEmpty(tempNamespace))
                    {
                        if (currentElement.Parent == null)
                        {
                            writer.WriteStartElement(currentElement.Prefix, currentElement.Name, tempNamespace);
                        }
                        else
                        {
                            if (_Independent)
                            {
                                writer.WriteStartElement(currentElement.Prefix, currentElement.Name, tempNamespace);

                            }
                            else
                            {
                                writer.WriteStartElement(currentElement.Name, tempNamespace);
                            }
                        }
                    }
                    else
                    {
                        writer.WriteStartElement(currentElement.Name);
                    }

                }
                else
                {
                    if (!String.IsNullOrEmpty(currentElement.Namespaces.DefaultNamespace))
                    {
                        writer.WriteStartElement(null, currentElement.Name, currentElement.Namespaces.DefaultNamespace);
                    }
                    else
                    {
                        writer.WriteStartElement(currentElement.Name);
                    }
                }

                if (!String.IsNullOrEmpty(currentElement.Namespaces.DefaultNamespace))
                {
                    writer.WriteAttributeString("xmlns", null, null, currentElement.Namespaces.DefaultNamespace);
                }


                foreach (string prefix in currentElement.Namespaces.Prefixes)
                {
                    tempNamespace = currentElement.Namespaces.LookupNamespace(prefix);

                    if (!String.IsNullOrEmpty(tempNamespace))
                    {
                        writer.WriteAttributeString("xmlns", prefix, null, tempNamespace);
                    }
                }

                foreach (Attribute attr in currentElement.Attributes.Values)
                {
                    BuildXml(attr);
                }

                if (currentElement.ChildNodes.Count > 0)
                {
                    foreach (Node childNode in currentElement.ChildNodes)
                    {
                        BuildXml(childNode);
                    }

                }
                writer.WriteEndElement();

            }

            if (node.NodeType == NodeType.Text)
            {
                writer.WriteString(node.Value);
            }

            if (node.NodeType == NodeType.Attribute)
            {
                Attribute attr = (Attribute)node;
                if (String.IsNullOrEmpty(attr.Prefix))
                {
                    writer.WriteStartAttribute(attr.Name);
                }
                else
                {
                    string attrNamespace = attr.Parent.Namespaces.LookupNamespace(attr.Prefix);

                    if (!String.IsNullOrEmpty(attrNamespace))
                    {
                        writer.WriteStartAttribute(attr.Prefix, attr.Name, attrNamespace);
                    }
                    else
                    {
                        writer.WriteStartAttribute(attr.Name);
                    }

                }

                if (attr.ChildNodes.Count > 0)
                {
                    foreach (Node textNode in attr.ChildNodes)
                    {
                        if (textNode.NodeType == NodeType.Text)
                        {
                            BuildXml(textNode);
                        }
                    }
                }

                writer.WriteEndAttribute();
            }


        } // BuildXml

        #endregion
    }
}
